package org.legzo.castorranker.ranker;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.slf4j.LoggerFactory;

public class Player implements Serializable {
	private static final long serialVersionUID = 2687066283566551105L;

	private String name;

	// Maps to keep historic
	private SortedMap<Game, Long> rankings = new TreeMap<Game, Long>();
	private long ranking = 1200;

	/**
	 * Only way to instanciate a player
	 * 
	 * @param name
	 * @param ranking
	 */
	public Player(String name) {
		this.name = name;
	}

	public void beats(Game game, Player otherPlayer, int pointDiff) {
		long diff = EloModRanker.getDiff(ranking, otherPlayer.getRanking(), true, pointDiff);
		this.updateRanking(game, diff);
		otherPlayer.updateRanking(game, -diff);
		LoggerFactory.getLogger(this.getClass()).trace("{} beats {} wih a elo diff of {} for a current ranking of {}",
				new Object[] { this.getName(), otherPlayer.getName(), diff, ranking });
	}

	public void ties(Game game, Player otherPlayer) {
		long diff = EloModRanker.getDiff(ranking, otherPlayer.getRanking(), false, 0);
		this.updateRanking(game, diff);
		otherPlayer.updateRanking(game, -diff);
		LoggerFactory.getLogger(this.getClass()).trace("{} ties {} wih a elo diff of {} for a current ranking of {}",
				new Object[] { this.getName(), otherPlayer.getName(), diff, ranking });
	}

	public long getLastRankingVariation() {
		Game[] games = rankings.keySet().toArray(new Game[] {});
		Date date = games[games.length - 1].getDate();
		return getRankingVariationForDate(date);
	}

	public long getRankingVariationForDate(Date d) {
		long dRanking = 1200;
		for (Map.Entry<Game, Long> entry : rankings.entrySet()) {
			if (entry.getKey().getDate().before(d)) {
				dRanking = entry.getValue();
			} else {
				return entry.getValue() - dRanking;
			}
		}
		return 0;
	}

	public long getRanking() {
		return ranking;
	}

	public void updateRanking(Game game, long diff) {
		this.ranking += diff;
		this.rankings.put(game, ranking);
	}

	public String getName() {
		return name;
	}

	public SortedMap<Game, Long> getRankings() {
		return rankings;
	}

	public Game getLastPlayedGame(Date d) {
		Game lastGame = null;
		for (Game g : rankings.keySet()) {
			if (g.getDate().before(d) || g.getDate().equals(d)) {
				lastGame = g;
			} else {
				return lastGame;
			}
		}
		return lastGame;
	}

	@Override
	public String toString() {
		return "Player [name=" + name + ", ranking=" + ranking + "]";
	}

	public static final class RankingComparator implements Comparator<Player> {
		private final Date d;

		public RankingComparator(Date d) {
			this.d = d;
		}

		public int compare(Player o1, Player o2) {
			Game o1LastPlayedGame = o1.getLastPlayedGame(d);
			Game o2LastPlayedGame = o2.getLastPlayedGame(d);
			if (o1LastPlayedGame == null && o2LastPlayedGame == null)
				return 0;
			if (o1LastPlayedGame == null)
				return -1;
			if (o2LastPlayedGame == null)
				return 1;

			return -(o1.getRankings().get(o1LastPlayedGame)).compareTo(o2.getRankings().get(o2LastPlayedGame));
		}
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Player other = (Player) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}
